<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=2">
<meta name="theme-color" content="#222">
<meta name="generator" content="Hexo 4.2.1">
  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png">
  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
  <link rel="mask-icon" href="/images/logo.svg" color="#222">

<link rel="stylesheet" href="/css/main.css">


<link rel="stylesheet" href="/lib/font-awesome/css/all.min.css">

<script id="hexo-configurations">
    var NexT = window.NexT || {};
    var CONFIG = {"hostname":"www.lixiaofei.cn","root":"/","scheme":"Mist","version":"7.8.0","exturl":false,"sidebar":{"position":"left","display":"post","padding":18,"offset":12,"onmobile":false},"copycode":{"enable":false,"show_result":false,"style":null},"back2top":{"enable":true,"sidebar":false,"scrollpercent":false},"bookmark":{"enable":false,"color":"#222","save":"auto"},"fancybox":false,"mediumzoom":false,"lazyload":false,"pangu":false,"comments":{"style":"tabs","active":true,"storage":true,"lazyload":true,"nav":{"valine":{"order":-1}}},"algolia":{"hits":{"per_page":10},"labels":{"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}},"localsearch":{"enable":false,"trigger":"auto","top_n_per_article":1,"unescape":false,"preload":false},"motion":{"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}}};
  </script>

  <meta name="description" content="上篇文章中，我们通过一个例子，了解了如何从文本文件中读取数据，并做统计处理，简单的一个脚本能帮助我们解脱繁琐的工作，将宝贵的时间用在更重要的事情上。 作为练习还行，但作为真正有用的程序还有很大的差距，今天我们就来再进一步，让程序更智能更有用。 然后我们需要了解一种程序设计思维，这种思维方式，可以帮我们解决很多需求分析问题。 现在开始吧！">
<meta property="og:type" content="article">
<meta property="og:title" content="编程，真不难 6 —— 需求变了">
<meta property="og:url" content="http://www.lixiaofei.cn/2022-07-12-program-5/index.html">
<meta property="og:site_name" content="管窥程序">
<meta property="og:description" content="上篇文章中，我们通过一个例子，了解了如何从文本文件中读取数据，并做统计处理，简单的一个脚本能帮助我们解脱繁琐的工作，将宝贵的时间用在更重要的事情上。 作为练习还行，但作为真正有用的程序还有很大的差距，今天我们就来再进一步，让程序更智能更有用。 然后我们需要了解一种程序设计思维，这种思维方式，可以帮我们解决很多需求分析问题。 现在开始吧！">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="http://www.lixiaofei.cn/images/2022/program_6/01.png">
<meta property="og:image" content="http://www.lixiaofei.cn/images/2022/program_6/02.png">
<meta property="article:published_time" content="2022-07-11T22:15:06.000Z">
<meta property="article:modified_time" content="2022-08-26T02:45:35.066Z">
<meta property="article:author" content="太阳雪">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="http://www.lixiaofei.cn/images/2022/program_6/01.png">

<link rel="canonical" href="http://www.lixiaofei.cn/2022-07-12-program-5/">


<script id="page-configurations">
  // https://hexo.io/docs/variables.html
  CONFIG.page = {
    sidebar: "",
    isHome : false,
    isPost : true,
    lang   : 'zh-CN'
  };
</script>

  <title>编程，真不难 6 —— 需求变了 | 管窥程序</title>
  






  <noscript>
  <style>
  .use-motion .brand,
  .use-motion .menu-item,
  .sidebar-inner,
  .use-motion .post-block,
  .use-motion .pagination,
  .use-motion .comments,
  .use-motion .post-header,
  .use-motion .post-body,
  .use-motion .collection-header { opacity: initial; }

  .use-motion .site-title,
  .use-motion .site-subtitle {
    opacity: initial;
    top: initial;
  }

  .use-motion .logo-line-before i { left: initial; }
  .use-motion .logo-line-after i { right: initial; }
  </style>
</noscript>

</head>

<body itemscope itemtype="http://schema.org/WebPage">
  <div class="container use-motion">
    <div class="headband"></div>

    <header class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-container">
  <div class="site-nav-toggle">
    <div class="toggle" aria-label="切换导航栏">
      <span class="toggle-line toggle-line-first"></span>
      <span class="toggle-line toggle-line-middle"></span>
      <span class="toggle-line toggle-line-last"></span>
    </div>
  </div>

  <div class="site-meta">

    <a href="/" class="brand" rel="start">
      <span class="logo-line-before"><i></i></span>
      <h1 class="site-title">管窥程序</h1>
      <span class="logo-line-after"><i></i></span>
    </a>
      <p class="site-subtitle" itemprop="description">Today is a gift</p>
  </div>

  <div class="site-nav-right">
    <div class="toggle popup-trigger">
    </div>
  </div>
</div>




<nav class="site-nav">
  <ul id="menu" class="main-menu menu">
        <li class="menu-item menu-item-home">

    <a href="/" rel="section"><i class="fa fa-home fa-fw"></i>首页</a>

  </li>
        <li class="menu-item menu-item-about">

    <a href="/about/" rel="section"><i class="fa fa-user fa-fw"></i>关于</a>

  </li>
        <li class="menu-item menu-item-tags">

    <a href="/tags/" rel="section"><i class="fa fa-tags fa-fw"></i>标签</a>

  </li>
        <li class="menu-item menu-item-categories">

    <a href="/categories/" rel="section"><i class="fa fa-th fa-fw"></i>分类</a>

  </li>
        <li class="menu-item menu-item-archives">

    <a href="/archives/" rel="section"><i class="fa fa-archive fa-fw"></i>归档</a>

  </li>
  </ul>
</nav>




</div>
    </header>

    
  <div class="back-to-top">
    <i class="fa fa-arrow-up"></i>
    <span>0%</span>
  </div>
  <div class="reading-progress-bar"></div>

  <a href="https://github.com/alisx" class="github-corner" title="Follow me on GitHub" aria-label="Follow me on GitHub" rel="noopener" target="_blank"><svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>


    <main class="main">
      <div class="main-inner">
        <div class="content-wrap">
          

          <div class="content post posts-expand">
            

    
  
  
  <article itemscope itemtype="http://schema.org/Article" class="post-block" lang="zh-CN">
    <link itemprop="mainEntityOfPage" href="http://www.lixiaofei.cn/2022-07-12-program-5/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="image" content="/images/avatar.png">
      <meta itemprop="name" content="太阳雪">
      <meta itemprop="description" content="总结 技术 思考 想法">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="管窥程序">
    </span>
      <header class="post-header">
        <h1 class="post-title" itemprop="name headline">
          编程，真不难 6 —— 需求变了
        </h1>

        <div class="post-meta">
            <span class="post-meta-item">
              <span class="post-meta-item-icon">
                <i class="far fa-calendar"></i>
              </span>
              <span class="post-meta-item-text">发表于</span>

              <time title="创建时间：2022-07-12 06:15:06" itemprop="dateCreated datePublished" datetime="2022-07-12T06:15:06+08:00">2022-07-12</time>
            </span>
              <span class="post-meta-item">
                <span class="post-meta-item-icon">
                  <i class="far fa-calendar-check"></i>
                </span>
                <span class="post-meta-item-text">更新于</span>
                <time title="修改时间：2022-08-26 10:45:35" itemprop="dateModified" datetime="2022-08-26T10:45:35+08:00">2022-08-26</time>
              </span>

          
            <span id="/2022-07-12-program-5/" class="post-meta-item leancloud_visitors" data-flag-title="编程，真不难 6 —— 需求变了" title="阅读次数">
              <span class="post-meta-item-icon">
                <i class="fa fa-eye"></i>
              </span>
              <span class="post-meta-item-text">阅读次数：</span>
              <span class="leancloud-visitors-count"></span>
            </span>
  
  <span class="post-meta-item">
    
      <span class="post-meta-item-icon">
        <i class="far fa-comment"></i>
      </span>
      <span class="post-meta-item-text">Valine：</span>
    
    <a title="valine" href="/2022-07-12-program-5/#valine-comments" itemprop="discussionUrl">
      <span class="post-comments-count valine-comment-count" data-xid="/2022-07-12-program-5/" itemprop="commentCount"></span>
    </a>
  </span>
  
  

        </div>
      </header>

    
    
    
    <div class="post-body" itemprop="articleBody">

      
        <p><a href="https://mp.weixin.qq.com/s/DAzqzcJd3BiwNgmL9ajrsw" target="_blank" rel="noopener">上篇文章</a>中，我们通过一个例子，了解了如何从文本文件中读取数据，并做统计处理，简单的一个脚本能帮助我们解脱繁琐的工作，将宝贵的时间用在更重要的事情上。</p>
<p>作为练习还行，但作为真正有用的程序还有很大的差距，今天我们就来再进一步，让程序更智能更有用。</p>
<p>然后我们需要了解一种程序设计思维，这种思维方式，可以帮我们解决很多需求分析问题。</p>
<p>现在开始吧！</p>
<a id="more"></a>
<p>我们回顾一下上次的程序。</p>
<p>主要是对一个打卡日志文件的数据提取和简单分析。</p>
<p>如果要作为一个真正有实际用处的程序，还需要很多方面的功能和改善，比如自动读取打卡机上的文件、智能处理应该处理的打卡日志、将统计分析结果自动发送给相关人员、如果发生处理问题，及时上报问题等等。</p>
<p>这些都可以通过不断地优化，让程序功能更强，更智能。</p>
<p>为了达到这些目标，或者实现这些功能，我们将逐步展开，步步为营，让自己获得一个完整的软件开发能力体系。</p>
<h2 id="需求变了">需求变了</h2>
<p>有很多段子用需求变了来调侃程序员的窘境，确实，当需求不受控制，方向大变时，任何编程技能和手段都会失效，而这样巨大的变化和不确定性，往往是人祸大于天灾，比如项目背景、用户意图理解的不对，或者需求分析不到位，或者设计存在巨大漏洞等等。</p>
<p>如果是正常的合理的变化，就应该积极应对，甚至采用一些技术手段，和理论方法来拥抱变化。因为变化本身，正是推动我们这个世界不断前行的动力，有了变化，才有改进，才有发展，才有进步。</p>
<p>在上次的研究中，我们假设每天的打卡日志文件已经获得了，只要指定打卡文件的文件名就可以处理了。</p>
<p>而这次，我们需要让程序能自动从存放众多打卡文件的目录（下称 日志目录）中，找到应该处理的打卡日志文件。</p>
<p>这里有个问题，打卡机上的日志文件怎么到日志目录上的呢？</p>
<p>通常有三种可能性：</p>
<ol>
<li>打卡机自带网络服务器功能，可以通过网络访问到上面的日志目录</li>
<li>打卡机上可以自动将日志文件推送到指定的网络上的日志目录中</li>
<li>由一个服务器程序（也可以是人工）定时从打卡机上提取最新的日志文件，同步到日志目录中</li>
</ol>
<p>无论是哪一种情况，都对我们无关紧要，这次，我们只要知道从日志目录中获取日志文件就可以了。</p>
<p>那么如何实现让程序自己确定应该处理哪个文件呢？</p>
<h2 id="需求分析">需求分析</h2>
<p><strong>面对一个需求，不要急于实现，而是先要仔细地思考和设计解决方案</strong>。</p>
<p>与上次的实现相比，我们不能再将日志文件名，<strong>写死</strong> 在代码里了，因为最新的日志文件名是变化的。</p>
<blockquote>
<p><strong>将可变的部分写死在程序代码里，不是个好主意</strong>，这也是很多程序缺乏灵活性甚至不可维护的主要原因之一。</p>
</blockquote>
<p>于是对于新需求来说，需要解决的核心问题就是：<code>如何获取待处理的日志文件名</code>。</p>
<p>我们先将注意力从程序实现上移开，展开想象，<strong>将自己想成一段程序代码，试想一下面对这样的问题自己应该如何处理</strong>。</p>
<blockquote>
<p>把自己想象成一段程序，身临其境地思考问题，是个寻找解决方案的好方法</p>
</blockquote>
<p>因为我们知道日志文件名是以日期命名的（见上一篇中的假设），如果我们也知道已经处理到哪个文件了，只要找到比这个文件名的日期 <code>更大或者更后</code> 的一个文件，就是我们要处理的文件。</p>
<p>比如处理到了 <code>2022-07-20.txt</code>，如果存在 <code>2022-07-21.txt</code> 这个文件，那就是我们要处理的文件。</p>
<p>另外，如果我们从来没有处理过任何文件，那么目录中日期最小的那个文件就是我们要处理的文件。</p>
<p>问题又来了，我们如何知道已经处理了哪些文件，或者我们最后处理的文件名是什么呢？</p>
<p>现实当中，我们可以记住（保存在记忆里），或者写在一张纸上，也就是说，我们需要在一个地方记录一下 <code>处理到哪里</code> 这个重要的信息。</p>
<p>想到这里，对于新的需求的思考与设计就做完了。</p>
<p>整理一下思考的结果：</p>
<ol>
<li>找到上一次处理到的文件名</li>
<li>找到比这个文件名更大的文件名，如果找到即为需要处理的文件</li>
</ol>
<p>这就是满足需求的 <strong>解决方案</strong>。</p>
<h2 id="实现解决方案">实现解决方案</h2>
<p>除了写死之外，如何让程序知道应该处理哪个文件呢？</p>
<h3 id="为程序提供外部信息">为程序提供外部信息</h3>
<p>让程序知道处理到了哪个文件这个信息，属于程序的外部信息，因为和程序的处理逻辑没有太大关系，并且与程序的生命周期也没关系，即这样的信息在程序启动前以及结束后都会存在。</p>
<p>对于提供给程序的外部信息，一般我们还有两种选择。</p>
<p><strong>第一种：通过运行参数</strong>：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">python main.py 2022-07-12</span><br></pre></td></tr></table></figure>
<p>在命令行中，正常命令后面添加的数据就是程序的运行参数，上面命令行中，<code>main.py</code> 作为 <code>python</code> 的运行参数，而 <code>2022-07-12</code> 作为 <code>main.py</code> 的参数。</p>
<p>这种程序运行参数很普遍，即使在图形界面中也存在，而且作用巨大，比如笔者的 AutoCAD 程序快捷方式中就有运行参数：</p>
<p><img src="../images/2022/program_6/01.png" alt="运行参数"></p>
<p>目测参数的作用是指定产品版本和语言。</p>
<blockquote>
<p><strong>注意</strong>：很多恶意软件常常会利于这一点，在浏览器的快捷方式中加入特定运行参数，让浏览器打开后直接访问恶意网站等等</p>
</blockquote>
<p>不过，这种方式对我们帮助不大，因为可能需要每次改变输入参数，不是很方便。</p>
<p><strong>第二种：利用配置文件</strong>：</p>
<p>配置文件是为程序提供外部信息的常见方式，对于大部分软件来说，在安装时将其配置文件会放在计算机的特定位置， Windows 中，会在用户目录下，创建软件对应的隐藏文件夹来存放；Linux 和 Mac 中，会在 <code>/etc</code> 目录下存放配置文件。</p>
<p>配置文件可以提供更多更复杂的信息，比如这是 Nginx Web 服务器的配置信息：</p>
<p><img src="../images/2022/program_6/02.png" alt="Nginx 配置文件"></p>
<p>当然配置文件和内容也可以很简单，只要能满足需求，形式和内容可以随意。</p>
<p>对于我们的问题来说，我们可以选择文本文件，来记录一个最后处理的日志文件名。</p>
<p><strong>为什么不用程序中的 <code>变量</code> 来记录该处理哪个文件呢？</strong></p>
<p>这是因为，变量只在程序运行过程中有效，或者说变量是程序运行状态下创建的，在程序结束或者退出之后，所创建的变量以及打开的资源就会被释放，下次打开时程序将不记得上次变量中的内容了。就像我们的短时记忆。</p>
<p>不过对于面向对象的编程语言（Python 算是）来说，有种 <a href="https://baike.baidu.com/item/%E6%8C%81%E4%B9%85%E5%8C%96" target="_blank" rel="noopener" title="持久化技术">持久化技术</a>，可能在程序启动时恢复之前的变量内容，但本质，上也是将信息存储在外部，类似于配置文件。</p>
<p><strong>不让程序关闭行不行？</strong></p>
<p>理论上说是可以的，但是想要制造出永不关闭的应用程序，难度是非常大的，因为除了技术本身的复杂度之外，还需要保障计算机不会崩溃，不会断电等等很多外部问题，这一定是我们不愿意去直接解决的。</p>
<h3 id="读写配置信息">读写配置信息</h3>
<p>最简单的配置文件就是文本文件，我们只需要打开文本文件，读取其中的内容就可以。</p>
<p>当程序处理完成，再将处理过的文件名写入其中即可。</p>
<p>读取文件上一次处理日志文件时我们了解过，而如何写入呢？</p>
<p>很简单，只要改变一个参数就可以了：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">config = open(<span class="string">'config.txt'</span>, <span class="string">'w'</span>, encoding=<span class="string">'utf-8'</span>)</span><br><span class="line">config.write(<span class="string">'2022-07-20.txt'</span>)</span><br><span class="line">config.close()</span><br></pre></td></tr></table></figure>
<ul>
<li>第一句的意思是从当前目录下，打开一个 <code>config.txt</code> 文件，打开模式为 <strong>写入</strong>。请注意与读取文件相比，第二个参数从 <code>r</code> 变成了 <code>w</code>，<code>w</code> 其实就是 <strong>write</strong> 的首字母，含义是覆盖性写入。关于操作文件的 <code>open</code> 方法更多信息请参考：<a href="https://www.runoob.com/python/python-func-open.html" target="_blank" rel="noopener" title="Python open 函数">Python open 函数</a></li>
<li>第二句表示向文件中写入一个字符串，即最后处理的日志文件名。如果要写入一行的话，末尾需要添加特殊字符：换行符 <code>\n</code>，更多关于特殊字符的表示，请参考<a href="https://zhuanlan.zhihu.com/p/481432109" target="_blank" rel="noopener" title="转义字符">转义字符</a></li>
<li>第三句为关闭文件，这时会将内容写入硬盘上的文件，并保存。当然我们可以用 <code>with</code> 关键字来省去调用 <code>close</code> 方法，请参考<a href="https://mp.weixin.qq.com/s/DAzqzcJd3BiwNgmL9ajrsw" target="_blank" rel="noopener">上一篇文章</a>中的说明</li>
</ul>
<p>可以看到只要将 <code>open</code> 方法的第二个参数换 <code>w</code> 就能实现对文本文件的读取了。</p>
<p>另外，参数 <code>w</code> 还有个作用就是当要写入的文件不存在时，会自动创建这个文件。这就很方便了，因为我们第一次运行时，往往会忘记创建这个配置文件，这样相当于让程序可以 “自理” 了。</p>
<h3 id="检索文件">检索文件</h3>
<p>通过配置文件，我们就能得到上次处理到了哪里。下面就需要找到下一个要处理的文件是什么。</p>
<p>经过前面的需求分析，我们知道下一个文件名是一个比上次处理的文件名大的。</p>
<p>看到需要比较，我们就能知道需要用到 <strong>分支语句</strong>，<strong>字符串能像数字一样被比较吗？</strong></p>
<p>看看下面的语句：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">'a'</span> &lt; <span class="string">'b'</span></span><br><span class="line"><span class="string">'abc'</span> &lt; <span class="string">'bcd'</span></span><br><span class="line"><span class="string">'1'</span> &lt; <span class="string">'2'</span></span><br><span class="line"><span class="string">'2022-07-22'</span> &lt; <span class="string">'2022-07-28'</span></span><br></pre></td></tr></table></figure>
<p>能猜到结果会是 <code>True</code> 还是 <code>False</code> 吗？</p>
<p>答案是显而易见的，排在前面的字母小，后面的字母大，也就是说字符串可以相互比较。</p>
<p>为什么呢？</p>
<p>这就会涉及到数据基础编码规则 <a href="https://baike.baidu.com/item/ASCII/309296" target="_blank" rel="noopener" title="ASCII">ASCII</a> 码了，ASCII 中对 128 个字符进行了编码，对于数字和字母都是按照自然顺序从低到高进行编码的。</p>
<p>这样当字符串被比较时，就能按照它们字符的编码数字来进行比较了。排在前面的，即编码比较小的字符小，排在后面的，即编码比较大的字符大。</p>
<p>对于字符串来说，是通过逐个比较每个字符来确定大小的，比如 <code>abc</code> 和 <code>baa</code> 来比较，虽然 <code>baa</code> 的后两位字符比 <code>abc</code> 的小，但是由于 <code>b</code> 比 <code>a</code> 大，所以更大的字符串是 <code>baa</code>。</p>
<p>想一下电脑上常用的按名称排序功能，其实就是利于字符串的大小进行排序的。</p>
<blockquote>
<p>如果你仔细观察会发现一个有趣的现象，如果我们有大于 10 首的播放列表，文件名是数字编码，例如 <code>1.mp3</code>、<code>2.mp3</code>… <code>10.mp3</code>…，我们想从1开始按顺序播放，常常得到的顺序是 <code>1.mp3</code>、<code>10.mp3</code>… <code>2.mp3</code>，现在知道问什么了吧。只要在不足两位数的文件名前补零，比如 <code>01.mp3</code> 就不会排错了。</p>
</blockquote>
<hr>
<p>我们还需要解决一个问题，就是如何获取日志目录中有哪些文件名。</p>
<p>对于文件和目录的操作，操作系统提供了很多方法，这些方法也被各种编程语言封装成了便利的功能。</p>
<p>在 Python 中，<code>os</code> 这个 <code>库</code>，集成了很多文件和目录的操作方法。</p>
<blockquote>
<p><code>os</code> 是一个集成了操作系统各种功能的库，什么是 <strong>库</strong> 呢？简单说就是一系列相关的程序资源(方法、类等)的集合，以方便被其他程序使用。</p>
<p>类似的还有 <strong>包</strong>、<strong>模块</strong>，可以将它们理解成公共方法，在需要用到时将它们引入程序中就可以了。</p>
<p>更多关于 <strong>库、包、模块</strong> 的知识可参考 <a href="https://www.zhihu.com/question/30082392" target="_blank" rel="noopener" title="Python 模块、库、包的区别">Python 模块、库、包的区别</a></p>
</blockquote>
<p>其中 <code>listdir</code> 就可以用例罗列目录中的内容：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"></span><br><span class="line">filenames = os.listdir(<span class="string">'logs'</span>)</span><br><span class="line"></span><br><span class="line">print(filenames)</span><br></pre></td></tr></table></figure>
<p><code>listdir</code> 可以接受一个目录路径参数，如果这个路径存在，就会将其中的文件名和目录名，放入一个数组，返回出来。</p>
<p>通过 <code>print</code> 方法就可能看到目录中的内容（文件名和目录名）。</p>
<p>对我们来说，我们只需要遍历 <code>listdir</code> 返回的结果，找到合适的文件名就可以了。</p>
<p>开始之前还需要确定一个事情，就是<strong>数组中的文件名是排好序的吗</strong>？</p>
<p>如果不是排好序的，我们比较过程就会很麻烦。</p>
<p>按说下一个文件就是当前文件的日期加一，但实际情况可能会存在跳过节假日等情况，因此不能假设文件名是连续的。</p>
<p>如果排好顺序，只要找到我们的文件名，下一个或者上一个（取决于升序还是倒序排列）文件名就是目标文件名。</p>
<p>幸运的是，通过 <code>listdir</code> 获得的内容列表就是按照字符串，升序排列的。</p>
<p>下面只需要通过循环就可以找到目标文件名了：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> os</span><br><span class="line"></span><br><span class="line">lastfilename = <span class="string">'2022-07-20.txt'</span></span><br><span class="line"></span><br><span class="line">filenames = os.listdir(<span class="string">'logs'</span>)</span><br><span class="line"></span><br><span class="line">targetFilename = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> f <span class="keyword">in</span> filenames:</span><br><span class="line">    <span class="keyword">if</span> f &gt; lastfilename:</span><br><span class="line">        targetFilename = f</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> targetFilename:</span><br><span class="line">    print(<span class="string">'待处理文件:'</span>, targetFilename)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    print(<span class="string">'没有新的文件'</span>)</span><br></pre></td></tr></table></figure>
<ul>
<li><code>lastfilename</code> 中存放的是我们上一次处理到的文件名，这里忽略了从配置文件读取的代码逻辑</li>
<li>在 <code>for</code> 循环中，我们判断是否有比 <code>lastfilename</code> 更大的文件名，一旦找到就将文件名记录在 <code>targetFilename</code> 中，立即退出循环</li>
<li>最后，判断 <code>targetFilename</code>，如果有值，说明已经找到，如果没有，说明还没有更新的文件需要处理</li>
</ul>
<p>关于最后一点 <code>targetFilename</code> 的判断很重要，它属于<a href="https://baike.baidu.com/item/%E8%BE%B9%E7%95%8C%E5%80%BC%E5%88%86%E6%9E%90%E6%B3%95" target="_blank" rel="noopener" title="边界值分析">边界值分析</a>的范畴，处理好了边界值文件，当程序作为服务，反复运行时，也不会出错或者做没必要的运算。</p>
<h2 id="梳理与集成">梳理与集成</h2>
<p>现在回顾一下整个过程。</p>
<ul>
<li>
<p>第一步，从打卡日志目录中，获取一个待处理文件名；</p>
</li>
<li>
<p>第二步，处理日志文件，将处理结果显示出来；</p>
</li>
<li>
<p>第三步，记录最后一个已处理文件名；</p>
</li>
</ul>
<p>和上一次的处理相比，多了第一步和第三步。</p>
<p>对于第一步来说，<code>输入</code>是配置文件，<code>输出</code>是找到的待处理文件名（或者未找到）；</p>
<p>对于第二步来说，<code>输入</code>是待处理的文件名，<code>输出</code>是打卡统计结构；</p>
<p>对于第三步来说，<code>输入</code>是待处理的文件名，<code>输出</code>是记录完成结果；</p>
<p>对于整体处理来说，<code>输入</code>是配置文件，<code>输出</code>是打卡统计结果。</p>
<p>能发现什么？</p>
<p>我们能看到，无论是整体，还是部分，都是由 <strong>输入</strong>，<strong>处理</strong>，<strong>输出</strong> 三部分组成。</p>
<h3 id="函数">函数</h3>
<p>现在，回顾一下前面讲述过的 <code>函数</code>（<code>方法</code>），对于一个函数来说，也由输入、处理、输出这三部分组成，也就是说 函数就是一个程序处理的集合，或者一个单元。</p>
<p>对于比较小的处理过程，编写一个函数就够了，比如上一节中，我们统计打卡日志文件的过程，甚至没有定义一个函数，因为处理过程本身就相当于一个函数。</p>
<p>对于大的复杂的处理过程，可以由不同的函数组合起来，比如我们这次的程序，由三部分组成。</p>
<p>那么 python 里的函数如何定义呢？先看代码：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">fun</span><span class="params">(input1, input2)</span>:</span></span><br><span class="line">    print(input1, input2)</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">True</span></span><br></pre></td></tr></table></figure>
<ul>
<li><code>def</code> 关键字可以定义一个函数，函数名是 <code>fun</code>，和变量一样，可以根据实际情况命名</li>
<li><code>fun</code> 后面的括号里，定义的是参数，也就是函数的 <strong>输入</strong> ，可以不定义任何输入参数</li>
<li>函数定义的下一行，比定义缩进了的部分是 <strong>处理</strong> 过程</li>
<li><code>return</code> 引导的是一个 <strong>输出</strong> ，如果不用 <code>return</code> 做显性输出的话，函数也输出，但输出的是 <code>None</code></li>
</ul>
<p>经过这样的定义，我们就 <strong>可以把任何过程写成函数的形式</strong>。</p>
<p>那么写成函数有什么好处呢？</p>
<ol>
<li>可以提供复用率，比如我们常用的 <code>print</code>，随时可用，不用每次再写一遍细节处理代码。</li>
<li>可以封装过程细节，让代码更简洁，更符合逻辑。</li>
<li>方便功能实现以及测试，因为函数内部具是一个封闭空间，功能集中，不会影响函数外的环境。</li>
</ol>
<h3 id="函数化">函数化</h3>
<p>现在我们将各个功能做一个函数化改造。</p>
<p>第一个是找到待处理文件：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">findTargetFileName</span><span class="params">(configFilename, logdir)</span>:</span></span><br><span class="line">    <span class="keyword">with</span> open(configFilename, <span class="string">'r'</span>, encoding=<span class="string">'utf-8'</span>) <span class="keyword">as</span> conf:</span><br><span class="line">        lastfilename = conf.read().strip()</span><br><span class="line"></span><br><span class="line">        filenames = os.listdir(logdir)</span><br><span class="line"></span><br><span class="line">        targetFilename = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> f <span class="keyword">in</span> filenames:</span><br><span class="line">            <span class="keyword">if</span> f &gt; lastfilename:</span><br><span class="line">                targetFilename = f</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">return</span> targetFilename</span><br></pre></td></tr></table></figure>
<ul>
<li>我们给查找待处理文件名，起的名字是 <code>findTargetFileName</code></li>
<li>可以接受两个参数，第一个是配置文件的名字，第二个是日志文件的目录</li>
<li>处理部分，首先打卡配置文件，获取到一行信息，然后获取日志目录的内容，并查找目标文件名</li>
<li>最后将获得到的文件名返回出来</li>
</ul>
<p>这个过程还可以拆分成获取配置信息 和 查找目标文件名两部分，这样可以减少程序对配置文件的依赖，请读者自行实践。</p>
<p>第二个是处理日志文件，获取统计结果：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">genReport</span><span class="params">(logFilename)</span>:</span></span><br><span class="line">    report = <span class="string">""</span></span><br><span class="line">    <span class="keyword">with</span> open(logFilename, <span class="string">"r"</span>, encoding=<span class="string">'utf-8'</span>) <span class="keyword">as</span> checkfile:</span><br><span class="line"></span><br><span class="line">        late_count = <span class="number">0</span> <span class="comment"># 迟到人数统计</span></span><br><span class="line">        absence_count = <span class="number">0</span> <span class="comment"># 缺勤人数统计</span></span><br><span class="line"></span><br><span class="line">        lates = [] <span class="comment"># 迟到员工编号</span></span><br><span class="line">        absences = [] <span class="comment"># 缺勤员工编号</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> l <span class="keyword">in</span> checkfile:</span><br><span class="line">            info = l.split(<span class="string">','</span>)</span><br><span class="line">            <span class="keyword">if</span> info[<span class="number">1</span>] == <span class="string">'迟到'</span>:</span><br><span class="line">                late_count += <span class="number">1</span> <span class="comment"># 累加迟到人数</span></span><br><span class="line">                lates.append(info[<span class="number">0</span>]) <span class="comment"># 记录迟到员工号</span></span><br><span class="line">            <span class="keyword">elif</span> info[<span class="number">1</span>] == <span class="string">'缺勤'</span>:</span><br><span class="line">                absence_count += <span class="number">1</span> <span class="comment"># 累加缺勤人数</span></span><br><span class="line">                absences.append(info[<span class="number">0</span>]) <span class="comment"># 记录缺勤员工号</span></span><br><span class="line">        report += <span class="string">f"迟到人数：<span class="subst">&#123;late_count&#125;</span>\n"</span></span><br><span class="line">        report += <span class="string">f"迟到员工号：<span class="subst">&#123;<span class="string">','</span>.join(lates)&#125;</span>"</span></span><br><span class="line">        report += <span class="string">f"缺勤人数：<span class="subst">&#123;absence_count&#125;</span>"</span></span><br><span class="line">        report += <span class="string">f"缺勤员工号：<span class="subst">&#123;<span class="string">','</span>.join(absences)&#125;</span>"</span></span><br><span class="line">    <span class="keyword">return</span> report</span><br></pre></td></tr></table></figure>
<ul>
<li><code>genReport</code> 用来生成打卡报告，接收一个打卡日志文件名作为参数</li>
<li>主体部分就是上一篇文章中统计的部分</li>
<li><code>report</code> 是统计结果，之前是直接打印到终端上的，这次将统计记录放在变量中，返回，这样相当于将结果的处理权交给了后续的其他处理</li>
</ul>
<p>最后我们需要将处理过的文件名，记录到配置文件里，为下一次处理提供方便：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">setConf</span><span class="params">(configFilename, content)</span>:</span></span><br><span class="line">    <span class="keyword">with</span> open(configFilename, <span class="string">'w'</span>, encoding=<span class="string">'utf-8'</span>) <span class="keyword">as</span> config:</span><br><span class="line">        config.write(content)</span><br></pre></td></tr></table></figure>
<ul>
<li><code>setConf</code> 接受两个参数，一个是配置文件名，和配置的内容</li>
<li>主体部分是打开配置文件，将内容写入其中</li>
</ul>
<p>到此我们就完成了整体处理过程的各个部分，接下来将这些部分根据业务需求集成起来。</p>
<h3 id="集成">集成</h3>
<p>根据业务要求，首先获取需要处理的文件，然后做统计处理，最后记录配置文件，这个和我们最初的分析是一致的，于是代码就可以写成：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">configfile = <span class="string">'config.txt'</span></span><br><span class="line">logdir = <span class="string">'log'</span></span><br><span class="line"></span><br><span class="line">targetFilename = findTargetFileName(configfile, logdir)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> targetFilename:</span><br><span class="line">    report = genReport(targetFilename)</span><br><span class="line">    print(report)</span><br><span class="line">    setConf(configfile, targetFilename)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    print(<span class="string">'没有需要处理的文件'</span>)</span><br></pre></td></tr></table></figure>
<ul>
<li>首先定义了必要的环境信息，比如配置文件的位置，日志目录的位置，虽然是写死在代码里的，但这部分信息不会频繁变化</li>
<li>获得目标文件名之后，不能直接用来生成报告，而是先要判断一下文件名是否为空，因为有可能没有需要处理的文件，这一点很重要</li>
<li>如果获取到了日志文件名，就生成报告，打印报告，并且将处理过的文件名记录到配置文件中</li>
<li>如果没有获取到日志文件，打印出一个提示</li>
</ul>
<p>到这一步，我们会发现，我们回到了这个任务的最开始，也就是所谓的业务描述部分。</p>
<p>这段集成在一起的代码，恰恰就是一个业务的描述，而前面我们做的，只是不断地深入到一个个问题的细节中去，解决一个个具体的问题，而这一切都是为最后的业务描述所服务的。</p>
<h2 id="总结">总结</h2>
<p>现在回顾一下整个过程。</p>
<p>首先我们提出了一个新的业务情况，为了应对这种业务情况，我们做了业务分析，和解决方案。</p>
<p>然后，我们将解决方案分成不同的部分，并解决其中遇到的各种具体问题。</p>
<p>接着，研究各个组成部分的输入、输出，将它们编写成函数。</p>
<p>最后，按照业务描述，将组成部分的函数集成起来，完成了整个业务的实现。</p>
<p>这个过程可以适用于大部分的软件构造过程，称之为 <strong>自上而下设计，自下而上实现</strong>。了解了这个过程，理论上就可以构造出任何程序。</p>
<p>另外，需要重点强调的是 <strong>函数</strong>，它能将复杂的、庞大的、业务无关的内容封装起来，只展示出与其打交道的输入输出部分，即<strong>接口</strong>，让我们的过程更简洁，业务更清晰，还有让不同部分有了被复用的机会。</p>
<p>例如，在另外的日志文件处理过程中，报告生成方法 <code>genReport</code> 可以直接被复用。</p>
<p>下次，我们将会讨论如何将结果发送给相应的人，以及如何将程序做出一个服务。</p>
<p>笔者的水平有限，文章中难免出现各种错误和问题，恳请读者不吝批评、指正。十分感谢！</p>
<p>比心！</p>

    </div>

    
    
    

      <footer class="post-footer">

        

			<div id="wechat_subscriber" style="display: block; padding: 10px 0; margin: 20px auto; width: 100%; text-align: center">
    <img id="wechat_subscriber_qcode" src="/images/wechat-qcode.png" alt="太阳雪 wechat" style="width: 200px; max-width: 100%;"/>
    <div>关注我，获得更多精彩内容</div>
</div>

        
		
    <div class="post-nav">
      <div class="post-nav-item">
    <a href="/2022-06-27-program-4/" rel="prev" title="编程，真不难 5 —— 初出茅庐">
      <i class="fa fa-chevron-left"></i> 编程，真不难 5 —— 初出茅庐
    </a></div>
      <div class="post-nav-item">
    <a href="/2022-08-25-program-6/" rel="next" title="编程，真不难 6 —— 不一样的输出">
      编程，真不难 6 —— 不一样的输出 <i class="fa fa-chevron-right"></i>
    </a></div>
    </div>
      </footer>
    
  </article>
  
  
  



          </div>
          
    <div class="comments" id="valine-comments"></div>

<script>
  window.addEventListener('tabs:register', () => {
    let { activeClass } = CONFIG.comments;
    if (CONFIG.comments.storage) {
      activeClass = localStorage.getItem('comments_active') || activeClass;
    }
    if (activeClass) {
      let activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`);
      if (activeTab) {
        activeTab.click();
      }
    }
  });
  if (CONFIG.comments.storage) {
    window.addEventListener('tabs:click', event => {
      if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return;
      let commentClass = event.target.classList[1];
      localStorage.setItem('comments_active', commentClass);
    });
  }
</script>

        </div>
          
  
  <div class="toggle sidebar-toggle">
    <span class="toggle-line toggle-line-first"></span>
    <span class="toggle-line toggle-line-middle"></span>
    <span class="toggle-line toggle-line-last"></span>
  </div>

  <aside class="sidebar">
    <div class="sidebar-inner">

      <ul class="sidebar-nav motion-element">
        <li class="sidebar-nav-toc">
          文章目录
        </li>
        <li class="sidebar-nav-overview">
          站点概览
        </li>
      </ul>

      <!--noindex-->
      <div class="post-toc-wrap sidebar-panel">
          <div class="post-toc motion-element"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#需求变了"><span class="nav-number">1.</span> <span class="nav-text">需求变了</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#需求分析"><span class="nav-number">2.</span> <span class="nav-text">需求分析</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#实现解决方案"><span class="nav-number">3.</span> <span class="nav-text">实现解决方案</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#为程序提供外部信息"><span class="nav-number">3.1.</span> <span class="nav-text">为程序提供外部信息</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#读写配置信息"><span class="nav-number">3.2.</span> <span class="nav-text">读写配置信息</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#检索文件"><span class="nav-number">3.3.</span> <span class="nav-text">检索文件</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#梳理与集成"><span class="nav-number">4.</span> <span class="nav-text">梳理与集成</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#函数"><span class="nav-number">4.1.</span> <span class="nav-text">函数</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#函数化"><span class="nav-number">4.2.</span> <span class="nav-text">函数化</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#集成"><span class="nav-number">4.3.</span> <span class="nav-text">集成</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#总结"><span class="nav-number">5.</span> <span class="nav-text">总结</span></a></li></ol></div>
      </div>
      <!--/noindex-->

      <div class="site-overview-wrap sidebar-panel">
        <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
    <img class="site-author-image" itemprop="image" alt="太阳雪"
      src="/images/avatar.png">
  <p class="site-author-name" itemprop="name">太阳雪</p>
  <div class="site-description" itemprop="description">总结 技术 思考 想法</div>
</div>
<div class="site-state-wrap motion-element">
  <nav class="site-state">
      <div class="site-state-item site-state-posts">
          <a href="/archives/">
        
          <span class="site-state-item-count">88</span>
          <span class="site-state-item-name">日志</span>
        </a>
      </div>
      <div class="site-state-item site-state-categories">
            <a href="/categories/">
          
        <span class="site-state-item-count">14</span>
        <span class="site-state-item-name">分类</span></a>
      </div>
      <div class="site-state-item site-state-tags">
            <a href="/tags/">
          
        <span class="site-state-item-count">47</span>
        <span class="site-state-item-name">标签</span></a>
      </div>
  </nav>
</div>



      </div>

    </div>
  </aside>
  <div id="sidebar-dimmer"></div>


      </div>
    </main>

    <footer class="footer">
      <div class="footer-inner">
        

        

<div class="copyright">
  
  &copy; 
  <span itemprop="copyrightYear">2022</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">太阳雪</span>
</div>
  <div class="powered-by">由 <a href="https://hexo.io/" class="theme-link" rel="noopener" target="_blank">Hexo</a> & <a href="https://mist.theme-next.org/" class="theme-link" rel="noopener" target="_blank">NexT.Mist</a> 强力驱动
  </div>

        








      </div>
    </footer>
  </div>

  
  <script src="/lib/anime.min.js"></script>
  <script src="/lib/velocity/velocity.min.js"></script>
  <script src="/lib/velocity/velocity.ui.min.js"></script>

<script src="/js/utils.js"></script>

<script src="/js/motion.js"></script>


<script src="/js/schemes/muse.js"></script>


<script src="/js/next-boot.js"></script>




  
  <script>
    (function(){
      var canonicalURL, curProtocol;
      //Get the <link> tag
      var x=document.getElementsByTagName("link");
		//Find the last canonical URL
		if(x.length > 0){
			for (i=0;i<x.length;i++){
				if(x[i].rel.toLowerCase() == 'canonical' && x[i].href){
					canonicalURL=x[i].href;
				}
			}
		}
    //Get protocol
	    if (!canonicalURL){
	    	curProtocol = window.location.protocol.split(':')[0];
	    }
	    else{
	    	curProtocol = canonicalURL.split(':')[0];
	    }
      //Get current URL if the canonical URL does not exist
	    if (!canonicalURL) canonicalURL = window.location.href;
	    //Assign script content. Replace current URL with the canonical URL
      !function(){var e=/([http|https]:\/\/[a-zA-Z0-9\_\.]+\.baidu\.com)/gi,r=canonicalURL,t=document.referrer;if(!e.test(r)){var n=(String(curProtocol).toLowerCase() === 'https')?"https://sp0.baidu.com/9_Q4simg2RQJ8t7jm9iCKT-xh_/s.gif":"//api.share.baidu.com/s.gif";t?(n+="?r="+encodeURIComponent(document.referrer),r&&(n+="&l="+r)):r&&(n+="?l="+r);var i=new Image;i.src=n}}(window);})();
  </script>















  

  


<script>
NexT.utils.loadComments(document.querySelector('#valine-comments'), () => {
  NexT.utils.getScript('//unpkg.com/valine/dist/Valine.min.js', () => {
    var GUEST = ['nick', 'mail', 'link'];
    var guest = 'nick,mail';
    guest = guest.split(',').filter(item => {
      return GUEST.includes(item);
    });
    new Valine({
      el         : '#valine-comments',
      verify     : false,
      notify     : false,
      appId      : 'teRj7yeJj0xpfGKLJ5uFdbIu-gzGzoHsz',
      appKey     : '3uie0p1nxouCCnkjiXTEBeLd',
      placeholder: "您的关注是我的动力",
      avatar     : 'mm',
      meta       : guest,
      pageSize   : '10' || 10,
      visitor    : true,
      lang       : 'zh-cn' || 'zh-cn',
      path       : location.pathname,
      recordIP   : false,
      serverURLs : ''
    });
  }, window.Valine);
});
</script>

</body>
</html>
